package joc.internal.transformer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import joc.customize.Contract;
import joc.customize.ForFieldEvaluateAtBeginningOfAllMethods;
import joc.customize.ForFieldEvaluateAtEndOfAllBehaviors;
import joc.internal.compiler.CastExp;
import joc.internal.compiler.NestedExp;
import joc.internal.compiler.StandaloneExp;

public class FieldInvariantTransformer extends AccessibleFieldTransformer {

	public FieldInvariantTransformer(Transformer transformer) {
		super(transformer);
	}

	@Override
	public void transform(CtClass clazz, CtField field) throws Exception {
		getLogger().debug("field " + field.getClass().getName() + "." + field.getName());
		for (Class<? extends Annotation> contractAnnotation : Transformer.getContractMap().keySet()) {
			Annotation annotation = (Annotation) field.getAnnotation(contractAnnotation);
			if (annotation != null) {
				getLogger().info(
						"field " + field.getClass().getName() + "." + field.getName() + " has contract "
								+ Transformer.getContractMap().get(contractAnnotation).getClass().getName());
				applyContractForField(clazz, field, Transformer.getContractMap().get(contractAnnotation),
						contractAnnotation, annotation);
			}
		}
	}

	private void applyContractForField(CtClass clazz, CtField field, Class<? extends Contract<?>> contractClass,
			Class<? extends Annotation> contractAnnotation, Annotation annotation) throws Exception {
		for (Method contractMethod : getContractMethodsWithAnnotation(contractClass,
				ForFieldEvaluateAtBeginningOfAllMethods.class)) {
			StandaloneExp contractCallExp = getContractCallString(field, contractMethod, contractClass,
					contractAnnotation, annotation, true,
					new CastExp(contractMethod.getParameterTypes()[1], NestedExp.field(field.getName())));
			for (CtMethod method : clazz.getDeclaredMethods()) {
				if (!Modifier.isStatic(method.getModifiers())) {
					contractCallExp.insertBefore(method);
					getLogger().info(
							"inserted " + contractClass.getClass().getName() + " in beginning of method "
									+ method.getLongName() + " for field " + field.getName());
				}
			}
		}
		for (Method contractMethod : getContractMethodsWithAnnotation(contractClass,
				ForFieldEvaluateAtEndOfAllBehaviors.class)) {
			StandaloneExp contractCallExp = getContractCallString(field, contractMethod, contractClass,
					contractAnnotation, annotation, true,
					new CastExp(contractMethod.getParameterTypes()[1], NestedExp.field(field.getName())));
			for (CtBehavior behavior : clazz.getDeclaredBehaviors()) {
				if (!Modifier.isStatic(behavior.getModifiers())) {
					contractCallExp.insertAfter(behavior);
					getLogger().info(
							"inserted " + contractClass.getClass().getName() + " in end of method "
									+ behavior.getLongName() + " for field " + field.getName());
				}
			}
		}
	}

}
